#ifdef MAODV

/*****************************************************************************
 *
 * Copyright (C) 2003 CSHCN, University of Maryland.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Authors: Dionysus Blazakis <dblaze@umd.edu>
 *          Jonathan Polley <jpolley@umd.edu>
 *          Dan Rusk <drusk@umd.edu>
 *          Manish Karir <karir@umd.edu>
 *          Jonathan McGee <mcgee@umd.edu>
 *
 *****************************************************************************/

#ifdef NS_PORT
#include "aodv-uu.h"
#else
#include <netinet/in.h>
#include "maodv_grph.h"
#include "aodv_socket.h"
#include "params.h"
#include "seek_list.h"
#include "defs.h"
#include "debug.h"
#include "multicast_routing_table.h"
#include "aodv_rreq.h"
#endif

GRPH *grph_create(u_int8_t flags, u_int8_t hcnt, u_int32_t grp_leader_addr, u_int32_t grp_addr, u_int32_t grp_seqno) 
{
    GRPH *grph;

    grph=(GRPH *)aodv_socket_new_msg();

    grph->type=MAODV_GRPH;
    grph->res1=0;
    grph->res2=0;
    grph->hcnt=hcnt;
    grph->grp_leader_addr=htonl(grp_leader_addr);
    grph->grp_addr=htonl(grp_addr);
    grph->grp_seqno=htonl(grp_seqno);

    if(flags & GRPH_UPDATE)
        grph->u=1;
    if(flags & GRPH_OFFMTREE)
        grph->o=1;

    DEBUG(LOG_DEBUG, 0, "Assembled GRPH %s", ip_to_str(grp_addr));
    if(grph->u){
        DEBUG(LOG_DEBUG, 0, "GRPH->u set");
    }

    return grph;
}

void grph_send(u_int8_t flags, u_int8_t hcnt, u_int32_t grp_addr, u_int32_t grp_seqno) 
{
    GRPH *grph;
    int i;
    /* ttl is one, packets get rebroadcast at each step */
    int ttl = NET_DIAMETER;

    for(i=0; i<MAX_NR_INTERFACES; i++) {
        if (!DEV_NR(i).enabled)
            continue;

        /* CHECK:  Will things break with different ip's per if for leader? */

        grph=grph_create(flags, hcnt, DEV_NR(i).ipaddr, grp_addr, grp_seqno);
        aodv_socket_send((AODV_msg *)grph, AODV_BROADCAST, GRPH_SIZE, ttl, &DEV_NR(i));
    }
}

void grph_forward(GRPH *grph, int ttl)
{
    int i;
    DEBUG(LOG_DEBUG, 0, "forwarding GRPH about %s", ip_to_str(ntohl(grph->grp_addr)));

    grph = (GRPH *) aodv_socket_queue_msg((AODV_msg *) grph, GRPH_SIZE);
    grph->hcnt++;
    for(i=0; i<MAX_NR_INTERFACES; i++) {
        if (!DEV_NR(i).enabled)
            continue;
        aodv_socket_send((AODV_msg *)grph, AODV_BROADCAST, GRPH_SIZE, ttl, &DEV_NR(i));
    }
}

/* special forwarding: unicast to your upstream */
void grph_forward_ucast(mrt_table_t *mrt, GRPH *grph, int ttl)
{
    mrt_nexthop_t *upstream;
    rt_table_t *rt;
    
    DEBUG(LOG_DEBUG, 0, "forwarding GRPH about %s upstream",
            ip_to_str(ntohl(grph->grp_addr)));

    upstream=mrt_table_get_activated_upstream(mrt);
    if(upstream != NULL){
        rt=rt_table_find(upstream->addr);
        grph->hcnt++;
        grph = (GRPH *) aodv_socket_queue_msg((AODV_msg *) grph, GRPH_SIZE);
        aodv_socket_send((AODV_msg *)grph, upstream->addr, GRPH_SIZE, ttl, &DEV_IFINDEX(rt->ifindex));
    }
}

void grph_process(GRPH *grph, u_int32_t src_addr, int ttl)
{
    mrt_table_t *mrt;
    u_int32_t grp_addr;
    u_int32_t grp_leader_addr;
    u_int32_t grp_seqno;
    int do_forward=0, do_forward_ucast=0;
    u_int8_t new_hcnt;

    grp_addr=ntohl(grph->grp_addr);
    grp_leader_addr=ntohl(grph->grp_leader_addr);
    grp_seqno=ntohl(grph->grp_seqno);
    new_hcnt=grph->hcnt+1;
    
    mrt=mrt_table_find(grp_addr);
    if(mrt==NULL){
        mrt=mrt_table_insert(grp_addr, grp_leader_addr, grp_seqno, 0, new_hcnt, 0);
        do_forward=1;
    }
    
    if(mrt->is_router){
        mrt_nexthop_t *upstream;
        upstream=mrt_table_get_activated_upstream(mrt);

        /* group leader doens't need to process grph */
        /* nor do I care about offmtree packets if I'm on the tree */
        if(mrt->am_leader){
            if(mrt->grp_leader_addr != grp_leader_addr){
                /* initiate tree merge */
                /* Check IP's */
                if ( ( mrt->grp_leader_addr < grp_leader_addr ) &&
                   ( !mrt->in_repair ) ){
                    /* my IP is less than his ;( */
                    /* initiate route discovery with all the flags */
                    DEBUG(LOG_DEBUG, 0, "Initiating tree merge for %s.", ip_to_str(grp_addr));

                    mrt->in_repair = 1;
                    rreq_route_discovery( grp_leader_addr, 
                        RREQ_JOIN | RREQ_REPAIR | RREQ_DEST_ONLY,
                        NULL, mrt->grp_addr );
                }
            }
        }else if(!grph->o){
            if(upstream != NULL && src_addr==upstream->addr){
                mrt->leader_hcnt=new_hcnt;
                mrt->grp_seqno=grp_seqno;

                if(grph->u || mrt->grp_leader_addr!=grp_leader_addr){
                    mrt_table_set_leader(mrt, grp_leader_addr, new_hcnt);
                }
                do_forward=1;
            }
        }
        
        if(!mrt->am_leader && grp_leader_addr != mrt->grp_leader_addr){
            /* if you get a grph for a different leader.... */
            /* unicast it to your upstream */
            do_forward_ucast=1;
        }
    }else{
        /* if not on the tree, you can be less discriminating about 
           your information */
        grph->o=1;
        /* potential method for forwarding grph */
        if(grp_seqno>mrt->grp_seqno || 
          (grp_seqno==mrt->grp_seqno && new_hcnt<mrt->leader_hcnt)){
            do_forward=1;
        }
        if(mrt->grp_leader_addr!=grp_leader_addr ||
          (mrt->grp_leader_addr==grp_leader_addr && new_hcnt<mrt->leader_hcnt)){
            mrt_table_set_leader(mrt, grp_leader_addr, new_hcnt);
            do_forward=1;
        }
        /* and finally update our sequence number */
        mrt->grp_seqno=grp_seqno;

    }

    if(do_forward && ttl>1){
        grph_forward(grph, --ttl);
    }

    if(do_forward_ucast && ttl>1){
        grph_forward_ucast(mrt, grph, --ttl);
    }

    if(this_host.gateway_mode && !mrt->is_router) {
        /* In Gateway mode, we want a piece of every group */
        mrt->is_gateway = 1;
        rreq_route_discovery( grp_addr, RREQ_JOIN, NULL, 0 );
    }
}

void grph_send_callback(mrt_table_t *mrt){
    int flags=0;
    if(mrt->grph_update_cnt>0){
        flags=GRPH_UPDATE;
        mrt->grph_update_cnt--;
    }
    if(mrt->am_leader){
        grph_send(flags, 0, mrt->grp_addr, ++mrt->grp_seqno);
    
        timer_set_timeout(&(mrt->grph_timer), GROUP_HELLO_INTERVAL);
    }
}

#endif /* MAODV */
